#pragma once
#include "IRenderer.h"
#include "OpenGLContext.h"
#include "OpenGL/OpenGLVertexArrayObject.h"
#include "../core/materials/ShaderMaterial.h"
#include "../geometries/BufferGeometry.h"
#include "../PocelainPrerequisites.h"
#include "OpenGL/OpenGLRenderData.h"
#include "../lights/Light.h"
#include <queue>
#include "../core/InputGLFW.h"
#include "OpenGL/shaders/ShaderChunk.h"
POCELAIN_NAMESPACE_BEGIN
POCELAIN_EXPORT class OpenGLRenderer : public IRenderer
{
public:
    OpenGLRenderer(GLFWwindow *window , InputGLFW *input);
    virtual ~OpenGLRenderer();
    virtual void render(Scene *scene, Camera *camera)override;
    virtual void clearBuffer(ERenderBufferType bufferType) override;
    void setViewport(int x,int y,int width ,int height);
    void setClearColor(Color color){mClearColor = color;}
    void setClearAlpha(Real value){mClearAlpha = value;}
private:
    void initContext();
    void setupLights(Node3D *node);
    void compile(Node3D *node);
    void sort(Node3D *node,Camera *camera);
    OpenGLGeometryRenderData *reconstructRenderData(BufferGeometry *geometry);
    OpenGLMaterialRenderData *reconstructRenderData(
        RawShaderMaterial *material,
        const std::map<std::string,EUniformType> &attributeTypeMap);
    RefPtr<OpenGLVertexArrayObject> getVAO(
            OpenGLGeometryRenderData *geometryData,
            OpenGLMaterialRenderData *materialData);
    struct RenderQueueItem
    {
        BufferGeometry *geometry;
        Material *material;
        Matrix4 modelMatrix;
    };
    typedef std::list<RenderQueueItem> RenderQueue_t;
    void renderByQueue(RenderQueue_t &queue);
    void loadShaderFromLib(RawShaderMaterial *mat);

    // avoid repeat call when same state
    struct CommonRenderState
    {
        uint32_t renderCount = 0;
        uint32_t drawCount = 0;

        bool colorWrite;
        //    bool alphaTest;
        bool depthTest;
        bool depthWrite;
        bool stencilWrite;
        bool blend;

        //        GLenum stencilFunc;
        //        uint32_t stencilFuncMask;
        //        uint32_t stencilWriteMask;
        //        GLenum stencilFail;
        //        GLenum stencilZFail;
        //        GLenum stencilZPass;

        GLenum blending;
        GLenum blendDst;
        GLenum blendSrc;
        GLenum blendEquation;
        GLenum side;

    };

    struct GeometryMaterialItem
    {
        OpenGLGeometryRenderData *geometryRenderData;
        OpenGLMaterialRenderData *materialRenderData;

        GeometryMaterialItem() = default;
        GeometryMaterialItem(decltype (geometryRenderData) gr,
                             decltype (materialRenderData) mr)
        {
            POCELAIN_ASSERT(gr&&mr);
            geometryRenderData = gr;
            materialRenderData = mr;
        }
        ~GeometryMaterialItem() = default;
        bool operator < (const GeometryMaterialItem &other) const
        {
            return  geometryRenderData < other.geometryRenderData
                    ||materialRenderData < other.materialRenderData;
        }
        bool operator == (const GeometryMaterialItem &other) const
        {
            return  geometryRenderData == other.geometryRenderData
                    &&materialRenderData == other.materialRenderData;
        }
    };
    void setCurrentVAO(OpenGLVertexArrayObject *vao);
    //void setupLight(Light *light);
    void makeVAO(
            OpenGLGeometryRenderData *geometryData,
            OpenGLMaterialRenderData *materialData);

    OpenGLVertexArrayObject *mCurrentVAO;
    Color mClearColor;
    Real mClearAlpha = Real(1.0);
    GLFWwindow *mWindow = nullptr;
    InputGLFW *mInput = nullptr;
    OpenGLContext *mContext;
    std::map<GeometryMaterialItem, RefPtr<OpenGLVertexArrayObject>> mMapRenderDataVao;

    TFunction<void(IRenderData*)> *mFnGeoDataCallback = nullptr;
    TFunction<void(IRenderData*)> *mFnMatDataCallback = nullptr;
    void geometryRenderDataDestructCallback(IRenderData *renderData);
    void materialRenderDataDestructCallback(IRenderData *renderData);
    RenderQueue_t mRenderQueue;
    std::map<std::string,RefPtr<IUniform>> mMapName2LightUniform;
    std::map<std::string,RefPtr<IUniform>> mMapCommonUniform;
    std::map<std::string,EUniformType> mMapCommonUniformType;
};
POCELAIN_NAMESPACE_END
